package com.smart.common.commder;


import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.smart.common.annotation.DictEnum;
import com.smart.common.entities.constant.SmartConstant;
import com.smart.common.entities.option.SelectOption;
import com.smart.manager.system.entities.enums.DictRwState;
import com.smart.manager.system.entities.enums.DictState;
import com.smart.manager.system.entities.models.SystemApi;
import com.smart.manager.system.entities.models.SystemDict;
import com.smart.manager.system.entities.views.ModuleView;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 模块扫描器
 *
 * @author Administrator
 * @date 2022/09/09
 */
@Component
@RequiredArgsConstructor
@Log4j2
public class SmartScanner {


    @javax.annotation.Resource
    private ResourceLoader resourceLoader;

    private final ResourcePatternResolver resolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
    private final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);

    private final RequestMappingHandlerMapping requestMappingHandlerMapping;


    /**
     * 扫描当前系统包含的枚举字典
     *
     * @return {@link List}<{@link SystemDict}>
     */
    public List<SystemDict> sanAllEnum() {
        String name = this.getClass().getPackage().getName();
        String[] arr = name.split("\\.");
        String path = arr[0].concat(".").concat(arr[1]);
        String enumClsName = "/**/*.class";
        ArrayList<SystemDict> newList = new ArrayList<>();

        try {
            String concat = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX.concat(ClassUtils.convertClassNameToResourcePath(path).concat(enumClsName));

            Resource[] resources = resolver.getResources(concat);

            if (resources.length == 0) {
                return null;
            }

            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
                    boolean dictEnum = metadataReader.getAnnotationMetadata().hasAnnotation(DictEnum.class.getName());
                    String className = metadataReader.getClassMetadata().getClassName();
                    if (dictEnum) {
                        Map<String, Object> annotationAttributes = metadataReader.getAnnotationMetadata()
                                .getAnnotationAttributes(DictEnum.class.getName());
                        assert annotationAttributes != null;
                        String title = annotationAttributes.get("title").toString();
                        String code = StrUtil.subAfter(className, ".", true);
                        SystemDict dict = this.formatToSystemDict(className, code, title);
                        if (ObjectUtil.isNotEmpty(dict)) {
                            newList.add(dict);
                        }

                    }
                }
            }

        } catch (IOException e) {
            log.error("扫描系统枚举字典失败:" + e.getMessage());

        }

        return newList;


    }


    /**
     * 枚举数据转换成字典结构
     *
     * @param className 类名
     * @param code      代码
     * @param title     标题
     * @return {@link SystemDict}
     */
    private SystemDict formatToSystemDict(String className, String code, String title) {
        try {

            Class<Enum> aClass = (Class<Enum>) Class.forName(className);
            Enum[] enumConstants = aClass.getEnumConstants();
            Method getLabel = aClass.getMethod("getLabel");

            Method getTheme = aClass.getMethod("getTheme");
            ArrayList<SelectOption> items = new ArrayList<>();
            for (Enum enum1 : enumConstants) {
                //执行枚举方法获得枚举实例对应的值
                String label = (String) getLabel.invoke(enum1);
                String value = enum1.name();
                String theme = (String) getTheme.invoke(enum1);
                SelectOption dictOption = new SelectOption().setLabel(label).setValue(value).setTheme(theme).setDisabled(false);
                items.add(dictOption);
            }

            return new SystemDict().setCode(code).setTitle(title).setDescription(className)
                    .setRwState(DictRwState.READ).setState(DictState.CALLED).setItems(items);
        } catch (Exception e) {
            log.warn(className + "转换为数据字典失败");
            return null;
        }
    }


    /**
     * 扫描当前服务内的所有APi接口
     *
     * @return {@code List<SystemApi>}
     */
    public List<SystemApi> scanAllApi() {

        ArrayList<SystemApi> result = new ArrayList<>();
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        Set<Map.Entry<RequestMappingInfo, HandlerMethod>> entries = handlerMethods.entrySet();
        int sort = 1;
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : entries) {
            HandlerMethod method = entry.getValue();
            //找到所在的类信息
            Class<?> declaringClass = method.getMethod().getDeclaringClass();

            //不是Controller 包内的接口不进行统计
            String packageName = declaringClass.getPackageName();
            if (!packageName.contains("controller")) {
                continue;
            }
            //获取和服务名称
            String className = declaringClass.getName();
            String beanName = declaringClass.getName();
            if (beanName.contains(SmartConstant.ROOT_PACKAGES)) {
                //获取路径 由于一个方法可能对应多重方式 所以需要循环  但是在项目中其实只有路径
                PatternsRequestCondition patternsCondition = entry.getKey().getPatternsCondition();
                //请求方式集合
                Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();
                SaCheckPermission saCheckPermission = method.getMethodAnnotation(SaCheckPermission.class);
                Set<String> patterns = patternsCondition.getPatterns();
                boolean isApi = CollectionUtil.isNotEmpty(patterns) && ObjectUtil.isNotEmpty(saCheckPermission);
                if (isApi) {
                    ModuleView module = getModule(packageName);
                    SystemApi item = new SystemApi()
                            .setFun(method.getMethod().getName())
                            .setModule(module)
                            .setPkgName(packageName)
                            .setClsName(className)
                            .setCustom(false)
                            .setExpr(saCheckPermission.value()[0])
                            .setSort(sort);
                    methods.stream().map(Enum::name).findFirst().ifPresent(item::setRequestType);
                    patterns.stream().findFirst().ifPresent(item::setUrl);
                    ApiOperation apiOperation = method.getMethodAnnotation(ApiOperation.class);
                    Opt.ofNullable(apiOperation).ifPresent(x -> item.setTitle(x.value()));
                    String token = DigestUtil.md5Hex16(item.getRequestType().concat(item.getUrl()));
                    item.setToken(token);
                    result.add(item);
                    sort++;
                }

            }
        }


        return result;

    }



    public ModuleView getModule(String pkgName){
        String moduleCode = pkgName.split("\\.")[3];
        return new ModuleView().setCode(moduleCode);

    }
}
